﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SQLiteProvider.Enums;
using DatabaseExtension.ConnectionString;
using DatabaseExtension;

namespace SQLiteProvider.ConnectionString
{
    /// <summary>
    /// Provide Connection String information for SQLite database.
    /// <seealso cref="http://www.devart.com/dotconnect/sqlite/docs/Devart.Data.SQLite~Devart.Data.SQLite.SQLiteConnection~ConnectionString.html"/>
    /// </summary>
    public sealed class SQLiteConnectionInfo : ConnectionInfo
    {
        /// <summary>
        /// Get the SQLite Connection String Builder instance
        /// </summary>
        public System.Data.SQLite.SQLiteConnectionStringBuilder SQLiteConnectionStringBuilder
        {
            get
            {
                return (base.ConnectionStringBuilder as System.Data.SQLite.SQLiteConnectionStringBuilder);
            }
        }
        #region ConnectionStringBuilder Derived Parameter
        /// <summary>
        /// Gets/Sets the placeholder base schema name used for .NET Framework compatibility
        /// </summary>
        public String BaseSchemaName
        {
            get
            {
                return this.SQLiteConnectionStringBuilder.BaseSchemaName;
            }
            set
            {
                this.SQLiteConnectionStringBuilder.BaseSchemaName = value;
            }
        }
        /// <summary>
        /// if true Store GUID columns in binary form, else tore GUID columns as text
        /// </summary>
        public Boolean BinaryGUID
        {
            get
            {
                return this.SQLiteConnectionStringBuilder.BinaryGUID;
            }
            set
            {
                this.SQLiteConnectionStringBuilder.BinaryGUID = value;
            }
        }
        /// <summary>
        /// number of database disk pages that SQLite will hold in memory at once. Each page uses about 1.5K of memory. 
        /// The default cache size is 2000. If you are doing UPDATEs or DELETEs that change many rows of a database and 
        /// you do not mind if SQLite uses more memory, you can increase the cache size for a possible speed improvement.
        /// </summary>
        public int CacheSize
        {
            get
            {
                return this.SQLiteConnectionStringBuilder.CacheSize;
            }
            set
            {
                this.SQLiteConnectionStringBuilder.CacheSize = value;
            }
        }
        /// <summary>
        /// Define which datetime format will be used for storing datetime
        /// </summary>
        public System.Data.SQLite.SQLiteDateFormats DateTimeFormat
        {
            get
            {
                return this.SQLiteConnectionStringBuilder.DateTimeFormat;
            }
            set
            {
                this.SQLiteConnectionStringBuilder.DateTimeFormat = value;
            }
        }
        /// <summary>
        /// Gets/Sets the DateTime kind for the connection.
        /// </summary>
        public DateTimeKind DateTimeKind
        {
            get
            {
                return this.SQLiteConnectionStringBuilder.DateTimeKind;
            }
            set
            {
                this.SQLiteConnectionStringBuilder.DateTimeKind = value;
            }
        }
        /// <summary>
        /// Sets the default isolation level for transactions on the connection.
        /// </summary>
        public System.Data.IsolationLevel DefaultIsolationLevel
        {
            get
            {
                return this.SQLiteConnectionStringBuilder.DefaultIsolationLevel;
            }
            set
            {
                this.SQLiteConnectionStringBuilder.DefaultIsolationLevel = value;
            }
        }
        /// <summary>
        /// The time in seconds to wait while trying to execute a command 
        /// before terminating the attempt and generating an error. 
        /// 0 indicates no limit.
        /// </summary>
        public int DefaultTimeout
        {
            get
            {
                return this.SQLiteConnectionStringBuilder.DefaultTimeout;
            }
            set
            {
                this.SQLiteConnectionStringBuilder.DefaultTimeout = value;
            }
        }
        /// <summary>
        /// Determines whether the connection is automatically enlisted in the current distributed transaction. 
        /// The default value is true. SQLite engine itself doesn't support distributed transactions. 
        /// </summary>
        public Boolean Enlist
        {
            get
            {
                return this.SQLiteConnectionStringBuilder.Enlist;
            }
            set
            {
                this.SQLiteConnectionStringBuilder.Enlist = value;
            }
        }
        /// <summary>
        ///  True - Don't create the database if it does not exist, throw an error instead
        ///  False - Automatically create the database if it does not exist
        ///  Default value is True
        /// </summary>
        public Boolean FailIfMissing
        {
            get
            {
                return this.SQLiteConnectionStringBuilder.FailIfMissing;
            }
            set
            {
                this.SQLiteConnectionStringBuilder.FailIfMissing = value;
            }
        }
        /// <summary>
        ///  Gets/Sets the extra behavioral flags.
        /// </summary>
        public System.Data.SQLite.SQLiteConnectionFlags Flags
        {
            get
            {
                return this.SQLiteConnectionStringBuilder.Flags;
            }
            set
            {
                this.SQLiteConnectionStringBuilder.Flags = value;
            }
        }
        /// <summary>
        ///  If enabled, use foreign key constraints
        /// </summary>
        public Boolean ForeignKeys
        {
            get
            {
                return this.SQLiteConnectionStringBuilder.ForeignKeys;
            }
            set
            {
                this.SQLiteConnectionStringBuilder.ForeignKeys = value;
            }
        }
        /// <summary>
        /// <summary>
        /// An alternate to the data source property that uses the SQLite URI syntax.
        /// </summary>
        public String FullUri
        {
            get
            {
                return this.SQLiteConnectionStringBuilder.FullUri;
            }
            set
            {
                this.SQLiteConnectionStringBuilder.FullUri = value;
            }
        }
        /// <summary>
        /// Determines how SQLite handles the transaction journal file.
        /// </summary>
        public System.Data.SQLite.SQLiteJournalModeEnum JournalMode
        {
            get
            {
                return this.SQLiteConnectionStringBuilder.JournalMode;
            }
            set
            {
                this.SQLiteConnectionStringBuilder.JournalMode = value;
            }
        }
        /// <summary>
        /// If enabled, uses the legacy 3.xx format for maximum compatibility, but results in larger database sizes.
        /// </summary>
        public Boolean LegacyFormat
        {
            get
            {
                return this.SQLiteConnectionStringBuilder.LegacyFormat;
            }
            set
            {
                this.SQLiteConnectionStringBuilder.LegacyFormat = value;
            }
        }
        /// <summary>
        /// {size in pages} - Limits the maximum number of pages (limits the size) of the database. Default is 0.
        /// </summary>
        public int MaxPageCount
        {
            get
            {
                return this.SQLiteConnectionStringBuilder.MaxPageCount;
            }
            set
            {
                this.SQLiteConnectionStringBuilder.MaxPageCount = value;
            }
        }
        /// <summary>
        /// Query or set the page size of the database. 
        /// The page size must be a power of two between 512 and 65536 inclusive.
        /// </summary>
        public int PageSize
        {
            get
            {
                return this.SQLiteConnectionStringBuilder.PageSize;
            }
            set
            {
                this.SQLiteConnectionStringBuilder.PageSize = value;
            }
        }
        /// <summary>
        /// If true, by default, the SQLiteConnection object is drawn from the 
        /// appropriate pool or is created and added to the appropriate pool.
        /// </summary>
        public Boolean Pooling
        {
            get
            {
                return this.SQLiteConnectionStringBuilder.Pooling;
            }
            set
            {
                this.SQLiteConnectionStringBuilder.Pooling = value;
            }
        }
        /// <summary>
        ///  When enabled, the database will be opened for read-only access and writing will be disabled.
        /// </summary>
        public Boolean ReadOnly
        {
            get
            {
                return this.SQLiteConnectionStringBuilder.ReadOnly;
            }
            set
            {
                this.SQLiteConnectionStringBuilder.ReadOnly = value;
            }
        }
        /// <summary>
        ///  If enabled, apply the default connection settings to opened databases.
        /// </summary>
        public Boolean SetDefaults
        {
            get
            {
                return this.SQLiteConnectionStringBuilder.SetDefaults;
            }
            set
            {
                this.SQLiteConnectionStringBuilder.SetDefaults = value;
            }
        }
        /// <summary>
        ///  Gets/Sets the synchronization mode (file flushing) of the connection string. Default is "Normal".
        /// </summary>
        public System.Data.SQLite.SynchronizationModes SyncMode
        {
            get
            {
                return this.SQLiteConnectionStringBuilder.SyncMode;
            }
            set
            {
                this.SQLiteConnectionStringBuilder.SyncMode = value;
            }
        }
        /// <summary>
        ///  If enabled, attempt to resolve the provided data source file name to a full path before opening.
        /// </summary>
        public Boolean ToFullPath
        {
            get
            {
                return this.SQLiteConnectionStringBuilder.ToFullPath;
            }
            set
            {
                this.SQLiteConnectionStringBuilder.ToFullPath = value;
            }
        }
        /// <summary>
        /// An alternate to the data source property
        /// </summary>
        public String Uri
        {
            get
            {
                return this.SQLiteConnectionStringBuilder.Uri;
            }
            set
            {
                this.SQLiteConnectionStringBuilder.Uri = value;
            }
        }
        /// <summary>
        /// SQLite Database version
        /// </summary>
        public int Version
        {
            get
            {
                return this.SQLiteConnectionStringBuilder.Version;
            }
            set
            {
                this.SQLiteConnectionStringBuilder.Version = value;
            }
        }
        #endregion
        /// <summary>
        /// Manage MySQL Boolean through the Enumerator Flags. Set only if needed.
        /// </summary>
        public SQLiteFlags BooleanFlags
        {
            get
            {
                return this._BooleanFlags;
            }
            set
            {
                var tmp = this.SQLiteConnectionStringBuilder;
                tmp.BinaryGUID = value.HasFlag(SQLiteFlags.BinaryGUID);
                tmp.Enlist = value.HasFlag(SQLiteFlags.Enlist);
                tmp.FailIfMissing = value.HasFlag(SQLiteFlags.FailIfMissing);
                tmp.ForeignKeys = value.HasFlag(SQLiteFlags.ForeignKeys);
                tmp.LegacyFormat = value.HasFlag(SQLiteFlags.LegacyFormat);
                tmp.Pooling = value.HasFlag(SQLiteFlags.Pooling);
                tmp.ReadOnly = value.HasFlag(SQLiteFlags.ReadOnly);
                tmp.SetDefaults = value.HasFlag(SQLiteFlags.SetDefaults);
                tmp.ToFullPath = value.HasFlag(SQLiteFlags.ToFullPath);
                tmp.UseUTF16Encoding = value.HasFlag(SQLiteFlags.UseUTF16Encoding);
                this._BooleanFlags = value;
            }
        }
        private SQLiteFlags _BooleanFlags;

        /// <summary>
        /// Create encrypted SQLite connection
        /// </summary>
        /// <param name="server">Server name (Filename or :memory:)</param>
        /// <param name="pwd"></param>
        public SQLiteConnectionInfo(String server, String pwd)
            : base(Databases.SQLite, server, null, pwd)
        {
            this.CheckAssemblyImport();
        }
        /// <summary>
        /// Create unencrypted SQLite connection
        /// </summary>
        /// <param name="server">Server name (Filename or :memory:)</param>
        public SQLiteConnectionInfo(String server)
            : this(server, null)
        {
        }
        /// <summary>
        /// Creat new SQLiteConnectionStringBuilder based on Connection String Builder.
        /// </summary>
        /// <param name="lcsb">SQLite Connection String Builder</param>
        public SQLiteConnectionInfo(System.Data.SQLite.SQLiteConnectionStringBuilder lcsb)
            : base(Databases.SQLite, lcsb)
        {
            this.CheckAssemblyImport();
        }
        /// <summary>
        /// Create new SQLite Connection String Builder
        /// </summary>
        protected override System.Data.Common.DbConnectionStringBuilder GetConnectionStringbuilder()
        {
            this.ClientFactory = System.Data.SQLite.SQLiteFactory.Instance;
            var result = this.ClientFactory.CreateConnectionStringBuilder() as System.Data.SQLite.SQLiteConnectionStringBuilder;
            result.DataSource = this.Server;
            if (String.IsNullOrEmpty(base.Password) == false)
                result.Password = this.Password;
            return result;
        }

        private static String AssemblyLocation;
        private static System.Reflection.Assembly CurrentAssembly;
        /// <summary>
        /// Extract (if not exists) the sqlite interop assembly.
        /// </summary>
        private void CheckAssemblyImport()
        {
            String assemblyName = "SQLite.Interop.dll";
            if (CurrentAssembly == null)
                CurrentAssembly = this.GetType().Assembly;
            if(String.IsNullOrEmpty(AssemblyLocation))
                AssemblyLocation = CurrentAssembly.Location;
            var fi = new System.IO.FileInfo(AssemblyLocation);
            var interop = System.IO.Path.Combine(fi.DirectoryName, assemblyName);

            if (System.IO.File.Exists(interop) == false)
            {
                var list = CurrentAssembly.GetManifestResourceNames().Where(a => a.Contains(assemblyName)).ToList();
                if (list.Count > 0)
                {
                    using (var a = CurrentAssembly.GetManifestResourceStream(list[0]))
                    {
                        using (var sw = new System.IO.StreamWriter(interop) { AutoFlush = true })
                        {
                            a.CopyTo(sw.BaseStream, 1024);
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Create new Db Parameter
        /// </summary>
        /// <param name="name">Parameter Name</param>
        /// <param name="value">Parameter Value</param>
        /// <param name="type">Use System.Data.DbType for inserted parameter</param>
        /// <param name="isNullable">if true, empty object will be converted to DbNull.Value</param>
        /// <param name="Size">Object Size.</param>
        /// <returns>Return new DbParameter</returns>
        public override System.Data.Common.DbParameter CreateParameter(string name, object value, object type, bool isNullable, int Size = 0)
        {
            var param = this.ClientFactory.CreateParameter();
            param.ParameterName = name;
            param.Value = (isNullable && value == null) ? DBNull.Value : value;
            param.Size = Size;
            if (type is System.Data.DbType)
                param.DbType = (System.Data.DbType)type;
            else
                throw new ArgumentException("Could not recognize Db Type");
            param.IsNullable = isNullable;
            return param;
        }
        /// <summary>
        /// Create new Db Parameter. Autodetect parameter
        /// </summary>
        /// <param name="name">Parameter Name</param>
        /// <param name="value">Parameter Value</param>
        /// <param name="isNullable">if true, empty object will be converted to DbNull.Value</param>
        /// <param name="Size">Object Size.</param>
        /// <returns>Return new DbParameter</returns>
        public override System.Data.Common.DbParameter CreateParameter(string name, object value, bool isNullable, int Size = 0)
        {
            return this.CreateParameter(name, value, DbTypeConverter.ToDbType(value), isNullable, Size);
        }
    }
}
